package com.zyb.dfs.namenode.server;


import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 内存双缓冲
 *
 * @author zhonghuashishan
 */
public class DoubleBuffer {

    /**
     * 单块缓冲区的默认大小
     */
    public static final Long EDIT_LOG_BUFFER_LIMIT = 20 * 1024L;

    /**
     * 是专门用来承载线程写入edits log
     */
    private EditLogBuffer currentBuffer = new EditLogBuffer();
    /**
     * 专门用来将数据同步到磁盘中去的一块缓冲
     */
    private EditLogBuffer syncBuffer = new EditLogBuffer();

    /**
     * 当前这块缓冲区写入的最大的一个txid
     */
    private long startTxid = 1L;

    /**
     * 已经刷入磁盘中txid范围
     */
    private List<String> flushedTxids = new CopyOnWriteArrayList<String>();

    /**
     * 将editlog写入到内存缓存区
     *
     * @param log
     */
    public void write(EditLog log) throws Exception {
        currentBuffer.write(log);
    }

    /**
     * 交换两块缓冲区，为了同步内存数据到磁盘做准备
     */
    public void setReadyToSync() {
        EditLogBuffer tmp = currentBuffer;
        currentBuffer = syncBuffer;
        syncBuffer = tmp;
    }

    /**
     * 将syncBuffer缓冲区中的数据刷入磁盘中
     */
    public void flush() throws Exception {
        syncBuffer.flush();
        syncBuffer.clear();
    }

    /**
     * 判断当前缓冲区是否写满了，是否需要刷到磁盘
     *
     * @return
     */
    public boolean shouldSyncToDisk() {
        if (currentBuffer.size() >= EDIT_LOG_BUFFER_LIMIT) {
            return true;
        }
        return false;
    }

    /**
     * 获取已经刷入磁盘editslog数据
     *
     * @return
     */
    public List<String> getFlushedTxids() {
        return flushedTxids;
    }

    /**
     * 获取当前缓冲区里的数据
     *
     * @return
     */
    public String[] getBufferedEditsLog() {
        if (currentBuffer.size() == 0) {
            return null;
        }
        String editsLog = new String(currentBuffer.getBufferDate());
        return editsLog.split("\n");
    }

    /**
     * editlog缓冲区
     */
    class EditLogBuffer {
        /**
         * 字节数组io流
         */
        ByteArrayOutputStream buffer;

        /**
         * 上一次flush到磁盘的时候他的最大的txid是多少
         */
        long endTxid = 0L;

        public EditLogBuffer() {
            buffer = new ByteArrayOutputStream(EDIT_LOG_BUFFER_LIMIT.intValue() * 2);
        }

        /**
         * 将editlog日志写入缓冲区
         */
        public void write(EditLog log) throws Exception {
            endTxid = log.getTxid();
            buffer.write(log.getContent().getBytes());
            buffer.write("\n".getBytes());
            System.out.println("当前" + log.getContent());
            System.out.println("当前缓冲区的大小" + buffer.size());
        }

        /**
         * 获取缓冲区已经写入的字节数量
         *
         * @return
         */
        public Integer size() {
            return buffer.size();
        }

        /**
         * 将syncBuffer输入磁盘中
         */
        public void flush() throws Exception {
            String editLogFilePath = "/Users/admin/editslog/edits-" + startTxid + "-" + endTxid + ".log";
            flushedTxids.add(startTxid + "_" + endTxid);

            RandomAccessFile file = null;
            FileOutputStream out = null;
            FileChannel disLogFileChannel = null;

            try {
                file = new RandomAccessFile(editLogFilePath, "rw"); // 读写模式，数据写入缓冲区中
                out = new FileOutputStream(file.getFD());
                disLogFileChannel = out.getChannel();
                byte[] data = buffer.toByteArray();
                ByteBuffer databuffer = ByteBuffer.wrap(data);
                disLogFileChannel.write(databuffer);
                // 强制把数据刷入到磁盘
                disLogFileChannel.force(false);
            } catch (Exception e) {
                e.getStackTrace();
            } finally {
                if (out != null) {
                    out.close();
                }
                if (file != null) {
                    file.close();
                }
                if (disLogFileChannel != null) {
                    disLogFileChannel.close();
                }
            }
            startTxid = endTxid + 1;
        }

        public void clear() {
            // 清空掉内存缓冲
            buffer.reset();
        }

        /**
         * 获取内存缓冲区当前数据
         *
         * @return
         */
        public byte[] getBufferDate() {
            return buffer.toByteArray();
        }
    }
}